﻿<!--


/*#
# Copyright 2010 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code is not supported by Google
#
*/
-->

<!--
Sample .aspx code for Google Commerce Search to submit ShoppingAPI-Search queries 
 then parse out the responses for display. 
 Shows additive filtering and page navigation.  

 If you site has Search As You type enabled, this code will automatically show
 the suggestions.
 
 >>> This code is NOT designed for production deployment and has not been 
 throughly tested.  It should only be used to understand GCS features.  <<<


 Usage:
  
  Edit the file and specify the GCS instance ID (cx) and the API key you're 
  using.

Deploy on any IIS server with .NET 4+ and enter in your GCS cx number, API key
https://code.google.com/apis/console/
http://www.google.com/cse/commercesearch/manage

*/
-->

<%@ Page Language="c#"  AutoEventWireup="true" Async="true" AsyncTimeout="5"%>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Text" %>
<%@ Import Namespace="System.Web.UI" %>
<%@ Import Namespace="System.Web" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Threading" %>

<script  language="c#" runat="server"> 
//Edit the following and enter in your API KEY and CX number
String devkey = "YOUR API KEY";
String cx = "YOUR CX";    
String country = "us";
String language = "en"; 
</script>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">


    <head>
    <title>GCS Search</title>

<!-- FOR SAYT -->   
<link rel="stylesheet" href="http://www.google.com/cse/style/commerce_sayt.css" type="text/css" media="screen" />
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script src="https://apis.google.com/js/client.js" type="text/javascript"></script>
<script src="http://www.google.com/cse/intl/en-US/sayt.js" type="text/javascript"></script>

<!-- FOR UI -->  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
      <link rel="stylesheet" type="text/css" href="https://developers.google.com/css/screen.css" />
      <link href='//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700' rel='stylesheet' type='text/css'>   

	<style type="text/css">
	body.docs table {
	    border-collapse: collapse !important; 
	    border: 0px solid #BBBBBB  !important;
	    border-spacing: 0  !important;
	    margin: 0 0 0em  !important;
	}
	</style>         
	
	
	<script type="text/javascript">
	    // Google Analytics integration
	    // Enter you analytics ID here
	    var _gaq = _gaq || [];
	    _gaq.push(['_setAccount', 'YOUR ANALYTICS ID']);
	    _gaq.push(['_trackPageview']);

	    (function () {
	        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
	        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
	        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
	    })();
	</script>


	<script language="javascript">

	    //from http://blog.movalog.com/a/javascript-toggle-visibility/
	    // some script to toggle the visibility of the per-item details
	    var state = 'none';
	    function showhide(id, productID) {
	        var e = document.getElementById(id);
	        if (e.style.display == 'block')
	            e.style.display = 'none';
	        else
	            e.style.display = 'block';
	        sendProductID(productID);
	    }

	    //used to simulate sending google search requestID and item click
	    //simulation...normally this is done only when someone clicks on
	    //a search result	
		function sendProductID(productID)
		{
			_gaq.push(['_setCustomVar', 4, 'GCS_ProductID', productID, 3]);
			_gaq.push(['_trackPageview']);
		}
		function sendRequestID(requestID)
		{
			_gaq.push(['_setCustomVar', 3, 'GCS_RequestID', requestID, 3]);
			_gaq.push(['_trackPageview']);
		}	

	    function initialize()
	    { }
	</script>
	
    </head>

    <script runat=server>
       String outbound_shopping = "";
       String requestID= "";
       String remove_filters = "";
       String totalResults = "";
       String page_nav = "";
       String html_attributes = "";
       String html_results = "";
       String coded_restrict = "";
       int TOTAL_PAGES_TO_SHOW = 20;
        
       String q = "";
       String startIndex = "";
       String restrictby = "";
       String orderby = "";
       String sortorder = "";
       string time_taken = "";
       Hashtable kvpairs = new Hashtable();
       DateTime startTime;
       DateTime endTime;
        
    System.Net.WebRequest _request_shopping;

    void Page_Load(object sender, EventArgs e)
    {
        q = Request.QueryString["q"];
        startIndex = Request.QueryString["startIndex"];
        restrictby = Request.QueryString["restrictBy"];
        orderby = Request.QueryString["sortOrder"];
        sortorder = Request.QueryString["sortOrder"];


        //the number of page links to show in the search result
        int TOTAL_PAGES_TO_SHOW = 20;

        // creates an array of all request GET parameters
        foreach (string key in Request.QueryString)
            if (key != null)
                kvpairs.Add(key,Request.QueryString[key]);

	    String userIP = Request.ServerVariables["REMOTE_ADDR"];
	    
        // the outbound query to send to GCS
        String shopping_base_query = "https://www.googleapis.com/shopping/search/v1/cx:" + cx + "/products?facets.enabled=true&country=" + country + "&maxResults=10&facets.useGcsConfig=true&facets.discover=20:10&alt=atom&rankBy=relevancy&userIP=" + userIP;

        // if not specified, initialize the variables for the call

        // escape the URL query string for languages
        if (q != null)
            q = "&q=" + Server.UrlEncode(q);
        else
            q = "";

        if (startIndex != null)
            startIndex = "&startIndex=" + startIndex;
        else
            startIndex = "";

        if (orderby != "")
        {
            orderby = "";
            sortorder = "";
        }

        // now decode and then separate out each restrictBy= combined parameter
        // i.e, base64 decode first, then parse out the restrictBy= parameter 
        // into separate components
        coded_restrict = Request["restrictBy"];
        if (coded_restrict == null)
            coded_restrict = "";
        List<String> vrestricts = getRestricts(decode(coded_restrict));
        
   // for each restrict, reconstruct the query string to send to GCS
   if (restrictby != null)
   {
	   String tmp_restrict = "";
	   foreach (String s in vrestricts)
	   {
			   String cat = s.Split('=')[0];
			   String val = s.Split('=')[1];
			   tmp_restrict += cat.Replace(" ","+") + "=" + Server.UrlEncode(this.escapeAttribute(cat,val)) +",";		
	   }

	   // if all we have is param1=value1,param2=value,  remove the last comma
       if (tmp_restrict.EndsWith(","))
           tmp_restrict = tmp_restrict.Substring(0, tmp_restrict.Length - 1);

	 if (tmp_restrict.Equals(""))
	   restrictby= "";
	 else
	   restrictby= "&restrictBy=" + tmp_restrict;
   }
   else
	restrictby = "";


        
   // now send the query to GCS
   outbound_shopping = shopping_base_query + "&key=" + devkey + (startIndex + q  + orderby + sortorder + restrictby);
   startTime = DateTime.Now;
        AddOnPreRenderCompleteAsync(
        new BeginEventHandler(BeginShoppingAsyncOperation),
        new EndEventHandler(EndShoppingAsyncOperation));
        
    }

    IAsyncResult BeginShoppingAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state)
    {
        _request_shopping = WebRequest.Create(outbound_shopping);
        // WebProxy wp = new WebProxy("yourproxyserver");
        //    _request_shopping.Proxy = wp;
        return _request_shopping.BeginGetResponse(cb, state);
    }

    void EndShoppingAsyncOperation(IAsyncResult ar)
    {
        List<Entry> lst_entry = new List<Entry>();
        List<Facet> lst_facet = new List<Facet>();
        try
        {
            string txtres;
            using (WebResponse response = _request_shopping.EndGetResponse(ar))
            {
                StreamReader rr = new StreamReader(response.GetResponseStream());
                using (TextReader reader = StreamReader.Synchronized(rr))
                {
                    txtres = reader.ReadToEnd();
                }
            }
            endTime = DateTime.Now;

            TimeSpan ts = endTime - startTime;
            time_taken = ts.TotalMilliseconds.ToString();

            XmlDocument xdoc = new XmlDocument();
            xdoc = new XmlDocument();
            xdoc.LoadXml(txtres);

            XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
            nsMgr.AddNamespace("openSearch", "http://a9.com/-/spec/opensearchrss/1.0/");
            nsMgr.AddNamespace("s", "http://www.google.com/shopping/api/schemas/2010");
            nsMgr.AddNamespace("atom", "http://www.w3.org/2005/Atom");
            nsMgr.AddNamespace("gd", "http://schemas.google.com/g/2005");

            XmlNode xtotalResults = xdoc.SelectSingleNode("/atom:feed/openSearch:totalResults", nsMgr);
            if (xtotalResults != null)
                totalResults = xtotalResults.FirstChild.Value;

            // this is the unique per-request ID number.  This token is unique to
            // each GCS call and should be 'sent back to GCS' through Google
            // Analytics integration.  
            // when someone searches for an item, the gcs response will contain
            // a requestId.  Once the user 'clicks on an item' in the result set,
            // you should send the requestID and the ProductID back via google
            // analytics (sendAnalytics() function)
            XmlNode n_requestID = xdoc.SelectSingleNode("/atom:feed/s:requestId", nsMgr);
            if (n_requestID != null)
                requestID = n_requestID.FirstChild.Value;

            XmlNodeList entries = xdoc.SelectNodes("/atom:feed/atom:entry/s:product", nsMgr);

            foreach (XmlNode entry in entries)
            {
                Entry objentry = new Entry();

                objentry.addAttribute(Entry.GOOGLE_ID, Entry.TYPE_TEXT, entry["s:googleId"].InnerText);
                objentry.addAttribute(Entry.PROVIDER_ID, Entry.TYPE_TEXT, entry["s:providedId"].InnerText);
                objentry.addAttribute(Entry.TITLE, Entry.TYPE_TEXT, entry["s:title"].InnerText);
                objentry.addAttribute(Entry.LINK, Entry.TYPE_TEXT, entry["s:link"].InnerText);
                objentry.addAttribute(Entry.DESC, Entry.TYPE_TEXT, entry["s:description"].InnerText);

                XmlNode inventories = entry["s:inventories"];
                XmlNodeList nl_inventory = inventories.SelectNodes("*", nsMgr);
                foreach (XmlNode ninventory in nl_inventory)
                {
                    string channel = ninventory.Attributes.GetNamedItem("channel").Value;
                    string unit = ninventory["s:price"].Attributes.GetNamedItem("currency").Value;
                    string price = ninventory["s:price"].InnerText;
                    objentry.addAttribute(Entry.PRICE, Entry.TYPE_TEXT, ninventory["s:price"].InnerText);
                }

                XmlNode images = entry["s:images"];
                if (images != null)
                    foreach (XmlNode image in images.ChildNodes)
                    {
                        string image_link = image.Attributes.GetNamedItem("link").Value;
                        objentry.addAttribute(Entry.IMAGE_LINK, Entry.TYPE_TEXT, image.Attributes.GetNamedItem("link").Value);
                    }
                else
                    objentry.addAttribute(Entry.IMAGE_LINK, Entry.TYPE_TEXT, null);

                XmlNode attributes = entry["s:attributes"];
                XmlNodeList nl_attribute = attributes.SelectNodes("*", nsMgr);
                foreach (XmlNode a in nl_attribute)
                {
                    string name = a.Attributes.GetNamedItem("name").Value;
                    string type = a.Attributes.GetNamedItem("type").Value;
                    string value = a.InnerText;
                    objentry.addAttribute(name, type, value);
                }
                lst_entry.Add(objentry);
            }

	        	// now parse out the facets	        	
            XmlNodeList nl_facet = xdoc.SelectNodes("/atom:feed/s:facets/s:facet", nsMgr);

            if (nl_facet != null)
                foreach (XmlNode n_facet in nl_facet)
                {
					Facet f = null;
					// Facets can be either standard properties or attributes.
					// see: http://code.google.com/apis/shopping/search/v1/reference-request-parameters.html#identifiers
					bool is_property = false;
					if (n_facet.Attributes.GetNamedItem("property") != null)
						is_property = true;
					else
						is_property = IsProperty(n_facet.Attributes.GetNamedItem("name").Value);
                    
                    if (is_property == false)
                    {
                        String name = n_facet.Attributes.GetNamedItem("name").Value;
                        String type = n_facet.Attributes.GetNamedItem("type").Value;
                        String count = n_facet.Attributes.GetNamedItem("count").Value;
                        String unit = null;
                        if (n_facet.Attributes.GetNamedItem("unit") != null)
                            unit = n_facet.Attributes.GetNamedItem("unit").Value;
                        f = new Facet(name, type, count, unit);
                        f.is_property = false;
                    }
                    else
                    {
                        String name = "";
                        if (n_facet.Attributes.GetNamedItem("property") != null)
                            name = n_facet.Attributes.GetNamedItem("property").Value;
                        else
                            name = n_facet.Attributes.GetNamedItem("name").Value;

                        String count = n_facet.Attributes.GetNamedItem("count").Value;
                        f = new Facet(name, null, count, null);
                        f.is_property = true;
                    }

                    // now get the facet values out
                    // again each facet value could either be a HISTOGRAM
                    // or straight key-value pairs
                    XmlNodeList nl_buckets = n_facet.SelectNodes("*", nsMgr);
                    foreach (XmlNode n_bucket in nl_buckets)
                    {
                        if (n_bucket.Attributes.GetNamedItem("value") != null)
                        {
                            // if key-values
                            String value = null;
                            String count = null;
                            value = n_bucket.Attributes.GetNamedItem("value").Value;
                            count = n_bucket.Attributes.GetNamedItem("count").Value;
                            f.addDiscreteBucket(value, count);
                        }
                        else
                        {
                            // if HISTOGRAM
                            String max = null;
                            String min = null;
                            String minExclusive = null;
                            String maxExclusive = null;
                            String count = null;
                            if (n_bucket.Attributes.GetNamedItem("max") != null)
                                max = n_bucket.Attributes.GetNamedItem("max").Value;
                            if (n_bucket.Attributes.GetNamedItem("min") != null)
                                min = n_bucket.Attributes.GetNamedItem("min").Value;
                            if (n_bucket.Attributes.GetNamedItem("maxExclusive") != null)
                                maxExclusive = n_bucket.Attributes.GetNamedItem("maxExclusive").Value;
                            if (n_bucket.Attributes.GetNamedItem("minExclusive") != null)
                                minExclusive = n_bucket.Attributes.GetNamedItem("minExclusive").Value;
                            if (n_bucket.Attributes.GetNamedItem("count") != null)
                                count = n_bucket.Attributes.GetNamedItem("count").Value;
                            f.addHistogramBucket(min, max, minExclusive, maxExclusive, count);
                        }
                    }
                    lst_facet.Add(f);                               							
	        }

            //to parse spelling suggestions (not implemented)
            XmlNodeList nl_suggest = xdoc.SelectNodes("/atom:feed/s:spelling/s:suggestion", nsMgr);

            //to parse promotions (not implemented)
            XmlNodeList nl_promotions = xdoc.SelectNodes("/atom:feed/s:promotions/s:promotion", nsMgr);            

            // parse out the store information if provided
            XmlNodeList nl_store = xdoc.SelectNodes("/atom:feed/s:stores/s:store", nsMgr);            
	               
	    // now for the complicated part:
	    // we need to carefully render the page
	    
	    // first get the original query string we got from the client
		String qstr = Request["q"];
		if (qstr != null)
			qstr = "&q=" + qstr;
		else
			qstr = "";
		
		// Create the 'remove filters' section where if the user
		// typed in a query string, it'll show up as a filter that you can
		// remove on the rendered page.
		// basically what this means is you will constrict a link
		// that has every parameter *except* the query param
		String filter_q = Request["q"];
		if (filter_q != null)
			remove_filters = "<a href='basic.aspx?"  +  constructQueryWithoutParameter(kvpairs,"q") + "'>" + filter_q + "</a>&nbsp;&nbsp;&nbsp;";

		// now get every other parameter except the restrictBy
		// because we need to do some special parsing on.
		String q_without_restrict = constructQueryWithoutParameter(kvpairs,"restrictBy");
	
		// now for every key=value entered INSIDE the &restrictBy, we need
		// recreate the query with every param *EXCEPT* the current one.
		// that is if the inboud query is restrictBy=key1=value1,key2=value2,key3=value3
		// we'll end up with three links:  
		// first one with a restrict only on key2=value2,key3=value3
		// first one with a restrict only on key1=value1,key3=value3
		// first one with a restrict only on key1=value1,key2=value2
		
		// NOTE instead of separating out the different restrictBy condidtions
		// with a ',' like it is in the SHoppingAPI, I just replaced it with 
		// a ^ to simplify the parsing (because a comma also exists inside
		// range restricts  

        // todo:define vrestricts only once...
        coded_restrict = Request["restrictBy"];
        if (coded_restrict == null)
            coded_restrict = "";
        List<String> vrestricts = getRestricts(decode(coded_restrict));
                        
		foreach (String s in vrestricts)
		{
			String excluded_restrict = "";
			foreach (String r in vrestricts)
				if (!r.Equals(s))
					excluded_restrict = excluded_restrict + r + "^";
			// again remove the trailing delimiter ^
			if (excluded_restrict.EndsWith("^"))
				excluded_restrict = excluded_restrict.Substring(0,excluded_restrict.Length-1);
			excluded_restrict = encode(excluded_restrict);
			// now render the 'remove filters link' item by item
			if (excluded_restrict.Equals(""))
				remove_filters += "<a href='basic.aspx?" + q_without_restrict + "'>" + s + "</a>&nbsp;&nbsp&nbsp";
			else
				remove_filters += "<a href='basic.aspx?" + q_without_restrict + "&restrictBy=" + excluded_restrict + "'>" + s + "</a>&nbsp;&nbsp&nbsp";
		}

        //now pagenav page_nav
        int aInt = Int32.Parse(totalResults);

        //then calculate the number of pages to show if there are 10 items per page.
        int num_pages = (int)Math.Ceiling((decimal)aInt / 10);
		
		// display the page navigation
		// again each link in the page navigation uses the querystring
		// to add on all the restricts that was sent in with the previous request
		int start_index=1;
		for (int jj=0; jj<num_pages; jj++) {
			if (jj==TOTAL_PAGES_TO_SHOW)
				break;
			int pnum = jj +1;
			page_nav += "<a href='basic.aspx?" + this.constructQueryWithoutParameter(kvpairs,"startIndex") + "&startIndex=" + start_index + "'>" + pnum + "</a>&nbsp;&nbsp;";
			start_index += 10;
		}            
            
            
            
            foreach (Entry e in lst_entry)
            {
				String slink = "";
				if (e.getAttributeValue(Entry.LINK).Count()>0)
					slink = (String)e.getAttributeValue(Entry.LINK).ElementAt(0);
				
				String stitle = "";
				if (e.getAttributeValue(Entry.TITLE).Count()>0)
					stitle = (String)e.getAttributeValue(Entry.TITLE).ElementAt(0);
				
				String simage_link = "";
				if (e.getAttributeValue(Entry.IMAGE_LINK).Count()>0)
					simage_link = (String)e.getAttributeValue(Entry.IMAGE_LINK).ElementAt(0);
				
				String sprice = "";
				if (e.getAttributeValue("s:price").Count()>0)
					sprice = (String)e.getAttributeValue("s:price").ElementAt(0);
				
				List<String[]> v_attributes = (List<String[]>)e.getAttributeVector();

                string div_id = Path.GetRandomFileName().Replace(".", "");
				
				// Always use inventory:price
                String strout = "<img height=100 width=100 src='" + simage_link + "'/>" + "<br/><a href='" + slink + "'>" + stitle + "</a><br/> price: " + (String)e.getAttributeValue(Entry.PRICE).ElementAt(0) + "<br/>";
                strout += "<div id='" + div_id + "' style=\"display:none\">";
                strout += "<table width='100%'>";
			    foreach (String[] kv in v_attributes)
    				strout += "<tr><td>[" + kv[0] + " (" + kv[1] + ") = "  + kv[2] + "]</td></tr>";
                
                strout += "</table>";
                strout += "</div><p><button type='button' onclick=\"showhide('" + div_id + "','" + e.getAttributeValue(Entry.PROVIDER_ID) + "');\">Show/Hide detail</button></p><br/>";                

                html_results += strout + "<hr>";
            }
            
            
            
		    // now to display the facets
	        foreach (Facet f in lst_facet)
	        {
	        	// create the query parameter without the restricts
	        	String qparams = this.constructQueryWithoutParameter(kvpairs,"restrictBy");
				
	        	// now decode it 
	        	// yeah, i could clean it up by combining the variables somehow
	        	// ...but this is just a demo
                String srestrict = Request.QueryString["restrictBy"];
				if (srestrict == null)
					srestrict = "";
				else
					srestrict = decode(coded_restrict);

				// create a restrict that is based on all the previous restricts
				// provided BUT it excludes f.getName()
				String s = constructRestrct(this.getRestricts(srestrict),f.getName());
				// add on the delimiter since wi'll use this later
				if (s.Length>0)
					s += "^";
				
				// check if the its not a property (its an attribute)
	        	if (!f.is_property)
	        	{
	        		// display the facet name as an attribute
		        	html_attributes  += "<font color='blue'>" + f.getName() + " (" + f.getPrimitive() + ")"  + ": [" + f.getCount() + "] </font><br/>";
		        	// even if its an attribute, it could be a histogram
		        	if (f.is_histogram)
		        	{
						// for each histogram bucket, generate a new restrict
						// in the form restrictBy=key(type)=(1,2]      		
						for (int i=0; i< f.getBucketCount(); i++)
						{
							Hashtable hm_row = f.getRowMap(i);
							String min = (String)hm_row["min"];
							String max = (String)hm_row["max"];
							String minExclusive = (String)hm_row["minExclusive"];					
							String maxExclusive = (String)hm_row["maxExclusive"];
							String count = (String)hm_row["count"];
							if (minExclusive == null && min == null)
							{
								minExclusive = "false";
								min = "0";
							}
							if (maxExclusive == null && max == null)
							{
								maxExclusive = "false";
								max = "*";
							}

							if (minExclusive.Equals("true"))
								minExclusive = "(";
							else
								minExclusive = "[";
			
							if (maxExclusive.Equals("true"))
								maxExclusive = ")";
							else
								maxExclusive = "]";
							
							String range;
			
							if (max.Equals(""))
								range = "<";
							else
								range = ",";
									
							// now, even if it a histogram, the attribute
							// may not have units (its just a number)
							// if its just a number, you don't need to add on
							// it can be in key(type)=value
							// if it  does have  units its key(type)/unit=value
							
							// now the variable "restrict" is real important
							// one that is used
							// escaped_restrict is just ignored entirely and
							// there to show the restrict that is in the URL.
							
							if (f.getUnit() != null)
							{
								String restrict = encode((s  +  f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min +  range + max + maxExclusive));
                                String escaped_restrict = (s + f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min + range + max + maxExclusive).Replace("&", "^").Replace("'", "%27");
								html_attributes = html_attributes + "&nbsp;&nbsp;<a href='basic.aspx?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict   + "'>"  +  f.getName() + "(" + f.getPrimitive() + ")/" + f.getUnit() + "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";							
							}
							else
							{
								String restrict = encode((s  +  f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min +  range + max + maxExclusive)) ;
                                String escaped_restrict = (s + f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min + range + max + maxExclusive).Replace("&", "^").Replace("'", "%27");								
								html_attributes = html_attributes + "&nbsp;&nbsp;<a href='basic.aspx?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict +  "'>"  +  f.getName() + "(" + f.getPrimitive() + ")" + "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";														
		        			}
						}
		        	}
		        	else
		        	{
		        		// now for non-histogramed attributes..just display
		        		// it in the form restrictBy=key(type)=value
		        		
						for (int i=0; i< f.getBucketCount(); i++)
						{
							Hashtable hm_row = f.getRowMap(i);
							String value = (String)hm_row["value"];
							String count = (String)hm_row["count"];	
							String restrict = encode((s  + f.getName() +  "(" + f.getPrimitive() + ")" + "=" + value));
                            String escaped_restrict = (s + f.getName() + "(" + f.getPrimitive() + ")" + "=" + value).Replace("&", "^").Replace("'", "%27");	
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='basic.aspx?" + qparams +  "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" + restrict + "'>"  + value + "  (" + count + ")" + "</a><br/>";
						}
		        	}
	        	}
	        	else
	        	{
	        		// for properties, encode:  restrictBy=name=value
		        	html_attributes  += "<font color='green'>" + f.getName() +  ": [" + f.getCount() + "] </font><br/>";	 
		        	if (f.is_histogram)
		        	{
						for (int i=0; i< f.getBucketCount(); i++)
						{
							Hashtable hm_row = f.getRowMap(i);
							String min = (String)hm_row["min"];
							String max = (String)hm_row["max"];
							String minExclusive = (String)hm_row["minExclusive"];					
							String maxExclusive = (String)hm_row["maxExclusive"];
							String count = (String)hm_row["count"];
							if (minExclusive == null && min == null)
							{
								minExclusive = "false";
								min = "0";
							}
							if (maxExclusive == null && max == null)
							{
								maxExclusive = "false";
								max = "*";
							}

                            if (minExclusive.Equals("true"))
								minExclusive = "(";
							else
								minExclusive = "[";

                            if (maxExclusive.Equals("true"))
								maxExclusive = ")";
							else
								maxExclusive = "]";
							
							String range;
			
							if (max.Equals(""))
								range = "<";
							else
								range = ",";
							String restrict = encode((s  +  f.getName() + "=" + minExclusive + min +  range + max + maxExclusive));
                            String escaped_restrict = (s + f.getName() + "=" + minExclusive + min + range + max + maxExclusive).Replace("&", "^").Replace("'", "%27");	
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='basic.aspx?" + qparams + "&raw_restrictBy=" + escaped_restrict + "&restrictBy=" +  restrict   +  "'>"  +  f.getName() +  "=" + minExclusive + min +  range + max + maxExclusive + "  (" + count  +")" + "</a><br/>";														
						}
		        	}
		        	else
		        	{
						for (int i=0; i< f.getBucketCount(); i++)
						{
                            Hashtable hm_row = f.getRowMap(i);
							String value = (String)hm_row["value"];
							String count = (String)hm_row["count"];
							String restrict = encode((s  + f.getName() +  "=" + value));
                            String escaped_restrict = (s + f.getName() + "=" + value).Replace("&", "^").Replace("'", "%27");							
							html_attributes = html_attributes + "&nbsp;&nbsp;<a href='basic.aspx?" + qparams +  "&raw_restrictBy=" + escaped_restrict +"&restrictBy=" + restrict  + "'>"  + value + "  (" + count + ")" + "</a><br/>";
						}
		        	}

	        	}
	        }
            
            
            
                                  
        }
        catch (Exception oe)
        {
            Response.Write("Begin -- " + oe.Message + "--" + oe.StackTrace);
        }           
    }        
        
    </script>


<body class="two-column docs" onload="initialize();sendRequestID('<%=requestID%>');">

    <div id="gc-wrapper">
    <div id="gc-main">
    <div id="gc-content">
    
<center>
	<img src="http://www.google.com/intl/en/images/logos/commerce_search_logo.gif" alt="Google Commerce Search"/>
    <form id="searchForm" method="GET">
	    <table>
    		<tr><td>search:</td><td><input id="q" name="q" type="text" size="45%" autocomplete="off" /></td></tr>     
		</table>	    
	    
		<input type="submit" value="Submit" "/>	
    </form>	
    
	<script type="text/javascript">

	    var searchText = document.getElementById('q');

	    var cx = '<%=cx%>';
	    var key = '<%=devkey%>';

	  google.load('search', '1');
	
	  var params = {
	    country: '<%=country%>',
	    language: '<%=language%>',
	    extra_params: {
	      safe: false
	    }
	  };
	
	  sayt = saytInitialize(cx, key, params);
	  
	  var style_options = {
	    xAlign: 'left'
	  };  
	
	  var opt_options = {
	    saytActor: sayt.saytSubmit,
	    styleOptions: style_options
	  };
	
	  google.setOnLoadCallback(function() {
	    google.search.CustomSearchControl.attachAutoCompletionWithOptions(
	      cx, searchText, 'searchForm', opt_options);
	  });
		</script>
</center>

<div style="border: 0px solid rgb(0, 0, 0); width: 80%;  overflow: auto;"  id="filters" name="filters">Filters: <%=remove_filters%></div>
<br/>
<td>Total Results:  <%=totalResults%></td>
<script src="http://www.google.com/jsapi" type="text/javascript"></script><td>Page:  <%=page_nav%></td>

<hr/>
		<table width="100%" border=2>
		<tr align="top" >

		  <td width="25%" id="attributes" name="attributes"  valign="top"><b><u>Attributes</u></b><br/><%=html_attributes%></td>

		  <td width="75%" id="results" name="results"  valign="top" ><b><u>Results</u></b><br/><%=html_results%></td>
		</tr>
		</table>

<font size="1px">
	<li><font color="blue">ShoppingAPI: </font> <a href='<%=outbound_shopping%>&prettyprint=true'><%=Server.UrlDecode(outbound_shopping)%></a></li>
	<li>RequestId: <%=requestID%></li>
	<li><font color="red">Time taken: <%=time_taken%> ms</font></li>
</font>

</div>
</div>
</div>
</body>
</html>

<script runat=server>
    
    
    // function to create a restrictBy string which omit a givne restrict.
    // vrestricts:  a vector of all the restricts sent in to the page
    // attribute:  the attribut to omit from the constructed restrict
    // returns: the restrictBy= string:
    // example given vrestricts that contains key1=value1  key2=value2 key3=value3
    // if the attribute parameter is 'key2' then this function returns:
    //  key1=value1^key3=value3

    private String constructRestrct(List<String> vrestricts, String attribute)
    {
	String restrict_string = "";
    if (((String)vrestricts.ElementAt(0)).Equals(""))
	   return "";
		foreach (string  s in vrestricts)
        {
            if ((s.Split('=')).Length > 0)
                if (!s.Split('=')[0].Equals(attribute))
                    restrict_string = restrict_string + s.Split('=')[0] + "=" + s.Split('=')[1] + "^";
            
		}
	if (restrict_string.Count()>0)
		return restrict_string.Substring(0, restrict_string.Count()-1);
	return restrict_string;
    }

    // returns a vector containing all the parsed individual restricts delimited by
    // a ^   for example, givne filter_restrict=key1=value1^key2=value2^key3=value3
    // returns a vector containing key1=value1   key2=value2  key3=value3

    private List<String> getRestricts(String filter_restricts)
    {
        List<String> v = new List<String>();
        string[] fltrs = filter_restricts.Split('^'); 
        for (int i = 0; i < fltrs.Length; i++)
        {
            String curr = fltrs[i];
            v.Add(curr);
        }
        return v;
    }
    
    // constructs a full query string given key-value but omits one of the parameters.
    // eg. if kvpairs contains key1,value1  key2,value2 key3,value3 key4,value4
    // and param_to_omit is key2,value2  the result is
    // key1=value1&key3=value3&key4=value4

 private String constructQueryWithoutParameter(Hashtable kvpairs, String param_to_omit)
 {
	String ret_qstr = "";	
    foreach (DictionaryEntry pair in kvpairs)
    {
        String key = (String) pair.Key;
		String value = (String) pair.Value;
		if (!key.Equals(param_to_omit) && !key.Equals("raw_restrictBy"))
        {
			if (key.Equals("q"))
					ret_qstr+= key + "=" + value + "&"; 
			else					
					ret_qstr+= key + "=" + value.Replace("+","%2B") + "&";
		}
	}
	return ret_qstr;
 }
        
// object holding price inventory data

class Inventory
{	
    public String channel = "";
    public String availability = "";
    
    public String currency = "";
    public String price = "";
    
    public String storeId = "";
    public String units = "";
    public String distance = "";
    
	public Inventory(String channel, String availability, String currency, String price) 
	{
		this.channel = channel;
		this.availability = availability;
		this.currency = currency;
		this.price = price;
	}	
    
	public Inventory(String channel, String availability, String currency, String price, String storeId, String units, String distance) 
	{
		this.channel = channel;
		this.availability = availability;
		this.currency = currency;
		this.price = price;
		this.storeId = storeId;
		this.units = units;
		this.distance = distance;
	}	
}

        
// object holding an actual item 

private class Entry
{
    public static String GOOGLE_ID = "s:googleId";
    public static String PROVIDER_ID = "s:providedId";
    public static String TITLE = "s:title";
    public static String LINK = "s:link";
    public static String DESC = "s:description";
    public static String PRICE = "s:price";
    public static String IMAGE_LINK = "s:images/@link";
    
    public static String TYPE_TEXT = "text";
    public static String TYPE_FLOAT = "float";
    public static String TYPE_INT = "int";
        
    List<Inventory> al_inventories = new List<Inventory>();
    List<String[]> v_entry_attributes = new List<String[]>();
    
    public Entry() {}
    
    public void addInventory(Inventory ins)
    {
    	al_inventories.Add(ins);
    }
   
    public List<Inventory> getInventoryList()
    {
    	return al_inventories;
    }
    
    public void addAttribute(String name, String type, String value)
    {
    	String[] a = new String[] {name,type,value};
    	v_entry_attributes.Add(a);
    }
    
    public String getAttributeType(String name)
    {
		foreach (String[] s in v_entry_attributes)
			if (s[0].Equals(name))
				return s[1];
        return null;
    }

    public List<String> getAttributeValue(String key)
    {
    	List<String> ret_values = new List<String>();
		foreach (String[] s in v_entry_attributes)
			if (s[0].Equals(key))
				ret_values.Add(s[2]);
		return ret_values;
    }

    public List<String[]> getAttributeVector()
    {
        return v_entry_attributes;
    }
    
    
}    
    
// checks to see if a given facet name is a property or attribute
//http://code.google.com/apis/shopping/search/v1/reference-request-parameters.html#identifiers

bool IsProperty(String c_name)
{
    return (c_name.Equals("accountId") || c_name.Equals("price") ||
            c_name.Equals("category") || c_name.Equals("brand") ||
            c_name.Equals("description") || c_name.Equals("condition") ||
            c_name.Equals("link") || c_name.Equals("price") ||
            c_name.Equals("title"));
}

// base64 encoder
private string encode(string data)
{
    try
    {
        byte[] encData_byte = new byte[data.Length];
        encData_byte = System.Text.Encoding.UTF8.GetBytes(data);    
        string encodedData = Convert.ToBase64String(encData_byte);
        return encodedData.Replace("+","%2B");
    }
    catch(Exception e)
    {
        throw new Exception("Error in base64Encode" + e.Message);
    }
}

public string decode(string data)
{
    try
    {
        System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();  
        System.Text.Decoder utf8Decode = encoder.GetDecoder();
        byte[] todecode_byte = Convert.FromBase64String(data);
        int charCount = utf8Decode.GetCharCount(todecode_byte, 0, todecode_byte.Length);    
        char[] decoded_char = new char[charCount];
        utf8Decode.GetChars(todecode_byte, 0, todecode_byte.Length, decoded_char, 0);                   
        string result = new String(decoded_char);
        return result;
    }
    catch(Exception e)
    {
        throw new Exception("Error in base64Decode" + e.Message);
    }
}


// function that escapes ShoppingAPI specific values:
// http://code.google.com/apis/commercesearch/docs/troubleshooting.html#5

String escapeAttribute(String key, String aa)
{
	// if the bucket is a histogram, don't escape it since we need
	// the special characters
	
	if (key.Contains("(int)") || key.Contains("(float)") || 
			key.Contains("raw_restrictBy") || this.IsProperty(key))
		return aa;
	
	aa = aa.Replace("\\", "\\\\");
	aa = aa.Replace("(", "\\(");
	aa = aa.Replace(")", "\\)");
	aa = aa.Replace("/", "\\/");
	aa = aa.Replace("=", "\\=");
	aa = aa.Replace(":", "\\:");
	aa = aa.Replace("|", "\\|");
	aa = aa.Replace("~", "\\~");
	//aa = aa.replaceAll("'","\\&#039;");
	return aa;
}

private class Facet
{
    private String name;
    private String primitive;
    private String count;
    private String unit = null;
    public bool is_property = false;
    public bool is_histogram = false;

    private List<Hashtable> lst_ranges = new List<Hashtable>();

    // <s:facet displayName="price" name="price" type="float" unit="usd" count="10"/>
    public Facet(String facet_name, String primitive, String count, String unit) 
    {
        this.name = facet_name;
        this.primitive = primitive;
        this.count = count;
		this.unit = unit;
    }

    public void addHistogramBucket(String min, String max, String minExclusive, String maxExclusive, String count) 
    {
	    Hashtable hm_histo = new Hashtable();
	    hm_histo.Add("min",min);
	    hm_histo.Add("max",max);
	    hm_histo.Add("minExclusive",minExclusive);
	    hm_histo.Add("maxExclusive",maxExclusive);
	    hm_histo.Add("count",count);
	    is_histogram = true;
	    lst_ranges.Add(hm_histo);
    }

    public void addDiscreteBucket(String name, String count) 
    {
	    Hashtable hm_discrete = new Hashtable();
	    hm_discrete.Add("value",name);
	    hm_discrete.Add("count",count);
	    lst_ranges.Add(hm_discrete);
    }

   public int getBucketCount()
   {
	return lst_ranges.Count();
   }

   public Hashtable getRowMap(int index)
   {
	return (Hashtable)lst_ranges.ElementAt(index);
   }
   
    public String getPrimitive()
    {
	return primitive;
    }

    public String getUnit()
    {
 		return unit;
    }
	
    public String getName()
    {
	return name;
    }

    public String getCount()
    {
        return this.count;
    }
}
    
    
</script>